package com.zige.zige.view.panoplayer.glwrapper;

import android.opengl.GLES30;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

/**
 * Created by admin on 16/1/5.
 */

public class GlGeometry {

    static public class VertexAttribs {
        public float[] position = new float[0];
        public float[] normal = new float[0];
        public float[] tangent = new float[0];
        public float[] binormal = new float[0];
        public float[] color = new float[0];
        public float[] uv0 = new float[0];
        public float[] uv1 = new float[0];
    }

    private int primitiveType;	// GL_TRIANGLES / GL_LINES / GL_POITNS / etc
    private int indexCount;
    private int[] vertexBuffer;
    private int[] indexBuffer;
    private int[] vertexArrayObject;

    public GlGeometry(VertexAttribs attribs, int[] indices) {

        this.primitiveType = GLES30.GL_TRIANGLES;

        indexCount = indices.length;

        vertexBuffer = new int[1];
        indexBuffer = new int[1];
        vertexArrayObject = new int[1];

        GLES30.glGenBuffers(1, vertexBuffer, 0);
        GLES30.glGenBuffers(1, indexBuffer, 0);
        GLES30.glGenVertexArrays(1, vertexArrayObject, 0);
        GLES30.glBindVertexArray(vertexArrayObject[0]);
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBuffer[0]);

        final int totalSize = 4 * (
                attribs.position.length
				+ attribs.normal.length
				+ attribs.tangent.length
				+ attribs.binormal.length
				+ attribs.color.length
				+ attribs.uv0.length
				+ attribs.uv1.length);

        FloatBuffer packed = ByteBuffer.allocateDirect(totalSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
        PackVertexAttribute(packed, attribs.position,   GlProgram.VertexAttributeLocation.POSITION.toInt(), 3);
        PackVertexAttribute(packed, attribs.normal,     GlProgram.VertexAttributeLocation.NORMAL.toInt(),   3);
        PackVertexAttribute(packed, attribs.tangent,    GlProgram.VertexAttributeLocation.TANGENT.toInt(),  3);
        PackVertexAttribute(packed, attribs.binormal,   GlProgram.VertexAttributeLocation.BINORMAL.toInt(), 3);
        PackVertexAttribute(packed, attribs.color,      GlProgram.VertexAttributeLocation.COLOR.toInt(), 4);
        PackVertexAttribute(packed, attribs.uv0,        GlProgram.VertexAttributeLocation.UV0.toInt(), 2);
        PackVertexAttribute(packed, attribs.uv1, GlProgram.VertexAttributeLocation.UV1.toInt(), 2);

        packed.position(0);
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, packed.capacity() * 4, packed, GLES30.GL_STATIC_DRAW);
        
        IntBuffer _indices = ByteBuffer.allocateDirect(indexCount * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
        _indices.put(indices).position(0);

        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, indexBuffer[0]);

        GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER, indexCount * 4, _indices, GLES30.GL_STATIC_DRAW);

        GLES30.glBindVertexArray(0);
        GLES30.glDisableVertexAttribArray(GlProgram.VertexAttributeLocation.POSITION.toInt());
        GLES30.glDisableVertexAttribArray(GlProgram.VertexAttributeLocation.NORMAL.toInt());
        GLES30.glDisableVertexAttribArray(GlProgram.VertexAttributeLocation.TANGENT.toInt());
        GLES30.glDisableVertexAttribArray(GlProgram.VertexAttributeLocation.BINORMAL.toInt());
        GLES30.glDisableVertexAttribArray(GlProgram.VertexAttributeLocation.COLOR.toInt());
        GLES30.glDisableVertexAttribArray(GlProgram.VertexAttributeLocation.UV0.toInt());
        GLES30.glDisableVertexAttribArray(GlProgram.VertexAttributeLocation.UV1.toInt());
    }

    static public GlGeometry sphere() {

        final int poleVertical = 3;
        final int uniformVertical = 64;
        final int horizontal = 128;
        final int vertical = uniformVertical + poleVertical * 2;
        final float radius = 2.0f;

        final int vertexCount = ( horizontal + 1 ) * ( vertical + 1 );

        VertexAttribs attribs = new VertexAttribs();
        attribs.position = new float[vertexCount * 3];
        attribs.uv0 = new float[vertexCount * 2];
        attribs.color = new float[vertexCount * 4];

        for ( int y = 0; y <= vertical; y++ )
        {
            float yf;
            if ( y <= poleVertical )
            {
                yf = (float) y / (poleVertical+1) / uniformVertical;
            }
            else if ( y >= vertical - poleVertical )
            {
                yf = (uniformVertical - 1 + ( (float)( y - (vertical - poleVertical - 1) ) / ( poleVertical+1) )) / uniformVertical;
            }
            else
            {
                yf = (float) ( y - poleVertical ) / uniformVertical;
            }
            final float lat = (float)(( yf - 0.5f ) * Math.PI);
            final float cosLat = (float)Math.cos( lat );
            for ( int x = 0; x <= horizontal; x++ )
            {
                final float xf = (float) x / (float) horizontal;
                final float lon = (float)(( 0.25f + xf ) * Math.PI * 2);
                final int index = y * ( horizontal + 1 ) + x;

                if ( x == horizontal )
                {
                    // Make sure that the wrap seam is EXACTLY the same
                    // xyz so there is no chance of pixel cracks.
                    attribs.position[3 * index] = attribs.position[3 * y * ( horizontal + 1 )];
                    attribs.position[3 * index + 1] = attribs.position[3 * y * ( horizontal + 1 ) + 1];
                    attribs.position[3 * index + 2] = attribs.position[3 * y * ( horizontal + 1 ) + 2];
                }
                else
                {
                    attribs.position[3 * index] = (float) (radius * Math.cos( lon ) * cosLat);
                    attribs.position[3 * index + 1] = (float) (radius * Math.sin( lat ));
                    attribs.position[3 * index + 2] = (float) (radius * Math.sin( lon ) * cosLat);
                }

                // With a normal mapping, half the triangles degenerate at the poles,
                // which causes seams between every triangle.  It is better to make them
                // a fan, and only get one seam.
                if ( y == 0 || y == vertical )
                {
                    attribs.uv0[2 * index] = 0.5f;
                }
                else
                {
                    attribs.uv0[2 * index] = xf;
                }
                attribs.uv0[2 * index + 1] = ( 1.0f - yf );
                attribs.color[4 * index]
                        = attribs.color[4 * index + 1]
                        = attribs.color[4 * index + 2]
                        = attribs.color[4 * index + 3] = 1.0f;
            }
        }

        int[] indices = new int[horizontal * vertexCount * 6];

        int index = 0;
        for ( int x = 0; x < horizontal; x++ )
        {
            for ( int y = 0; y < vertical; y++ )
            {
                indices[index + 0] = y * (horizontal + 1) + x;
                indices[index + 1] = y * (horizontal + 1) + x + 1;
                indices[index + 2] = (y + 1) * (horizontal + 1) + x;
                indices[index + 3] = (y + 1) * (horizontal + 1) + x;
                indices[index + 4] = y * (horizontal + 1) + x + 1;
                indices[index + 5] = (y + 1) * (horizontal + 1) + x + 1;
                index += 6;
            }
        }

        return new GlGeometry(attribs, indices);
    }

    static public GlGeometry line(float[] p1, float[] p2) {
        VertexAttribs attribs = new VertexAttribs();
        attribs.position = new float[6];
        for (int i = 0; i < 3; ++i) {
            attribs.position[i] = p1[i];
            attribs.position[2 * i] = p2[i];
        }
        attribs.color = new float[8];
        for (int i = 0; i < 8; ++i) attribs.color[i] = 1.0f;

        int[] indices = {0, 1};
        GlGeometry l = new GlGeometry(attribs, indices);
        l.setPrimitiveType(GLES30.GL_LINES);
        return l;
    }

    static public GlGeometry quad() {
        VertexAttribs attribs = new VertexAttribs();
        final float[] position = {
                -1.0f, 1.0f, 0.0f,
                -1.0f, -1.0f, 0.0f,
                1.0f, -1.0f, 0.0f,
                1.0f, 1.0f, 0.0f
        };
        final float[] uv0 = {
                0.0f, 1.0f,
                0.0f, 0.0f,
                1.0f, 0.0f,
                1.0f, 1.0f
        };
        final float[] color = {
                0.0f, 1.0f, 0.0f, 1.0f,
                0.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 0.0f, 0.0f, 1.0f,
                1.0f, 1.0f, 0.0f, 1.0f
        };
        int[] indices = {0, 1, 2, 0, 2, 3};
        attribs.position = position;
        attribs.uv0 = uv0;
        attribs.color = color;
        return new GlGeometry(attribs, indices);
    }

    public void setPrimitiveType(int primitiveType) {
        this.primitiveType = primitiveType;
    }

    public void draw() {
        GLES30.glBindVertexArray(vertexArrayObject[0]);
        GLES30.glDrawElements(primitiveType, indexCount, GLES30.GL_UNSIGNED_INT, 0);
    }

    public void free() {
        GLES30.glDeleteVertexArrays(1, vertexArrayObject, 0);
        GLES30.glDeleteBuffers(1, indexBuffer, 0);
        GLES30.glDeleteBuffers(1, vertexBuffer, 0);

        indexBuffer[0] = 0;
        vertexBuffer[0] = 0;
        vertexArrayObject[0] = 0;
        indexCount = 0;
    }

    private void PackVertexAttribute( FloatBuffer packed, float[] attrib, int glLocation, int glComponents ) {
        if ( attrib.length > 0 )
        {
            int offset = packed.position() * 4;
            packed.put(attrib);
            GLES30.glEnableVertexAttribArray( glLocation );
            GLES30.glVertexAttribPointer(glLocation, glComponents, GLES30.GL_FLOAT, false, glComponents * 4, offset);
        }
        else
        {
            GLES30.glDisableVertexAttribArray( glLocation );
        }
    }

}
